import { extend } from '../shared'

class ReactiveEffect {
  private _fn: any // 被private修饰的成员只允许在类的内部访问，子类也不具备访问性
  // public为类成员的默认可见性，被public修饰的成员可在class的内部和外部被访问
  deps = []
  active = true
  onStop?: () => void
  constructor(fn: Function, public scheduler?: Function) {
    this._fn = fn
  }
  run() {
    //当执行的时候赋值
    activeEffect = this
    return this._fn() // 返回这个参数运行的结果
  }

  stop() {
    // 多次调用stop时没有必要频繁清空
    if (this.active) {
      cleanupEffect(this)

      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

function cleanupEffect(effect) {
  effect.deps.forEach((dep: Set<ReactiveEffect>) => {
    dep.delete(effect)
  })
}

const targetMap = new Map()
/**
 * @description:依赖收集（ 在proxy set的时候）
 * @param {*} target
 * @param {*} key
 * @return {*}
 */
export function track(target, key) {
  //数据存储走向： target -> key ->dep
  //数据结构： Map（最好是weakMap）-》Map -》Set
  let depsMap = targetMap.get(target)

  //!初始话没有--走这
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }

  // 依赖函数收集
  let dep: Set<ReactiveEffect> = depsMap.get(key)

  //!初始话没有--走这
  if (!dep) {
    dep = new Set() //为什么是set？是因为我们在一个函数上面可以多次获取数据，但是依赖还是同一个函数，没有必要重复存储
    depsMap.set(key, dep)
  }
  if (!activeEffect) return
  
  dep.add(activeEffect) // 每一次set操作都会收集存储activeEffect(再有的情况下收集)
  activeEffect.deps.push(dep) //一个effect实例存储它所对应的deps
}

let activeEffect //存储一下effect实例的变量--// !什么时候才有activeEffect-》只有我们调用了effect函数才会去new一个新的reactiveEffect-》从而调用run的时候才会赋值它-》否则undefined
interface effectOptions {
  scheduler?: Function
  onStop?: () => void
}
export function effect(fn: Function, options: effectOptions = {}): Function {
  // 这里进行拦截一下，然后进行其它的依赖操作

  const _effect = new ReactiveEffect(fn, options.scheduler) //传入scheduler

  //options会有很多，优雅挂值
  extend(_effect, options)

  _effect.run() //当然也会进行原本函数的调用（runner函数）

  const runner: any = _effect.run.bind(_effect) //返回这个runner函数同时绑定this，以便之后调用
  runner.effect = _effect // 在runner上挂载实例
  return runner
}

/**
 * @description: 触发依赖
 * @param {*} target
 * @param {*} key
 * @return {*}
 */
export function trigger(target, key) {
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)

  for (const effect of dep) {
    // 更新的时候先看看有没有scheduler，先不急着直接执行fn
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

export function stop(runner) {
  // 我们的runner上有ReactiveEffect实例
  runner.effect.stop()
}
